import { Ionicons } from '@expo/vector-icons'; import { useLocalSearchParams, useRouter } from 'expo-router'; import { useState } from 'react'; import { ActivityIndicator, Alert, Pressable, ScrollView, StyleSheet, Text, TextInput, View, } from 'react-native'; import { SafeAreaView } from 'react-native-safe-area-context'; import Svg, { Circle, Line, Polygon, Text as SvgText } from 'react-native-svg'; import { useJobDetail } from '../../src/hooks'; import { colors } from '../../src/theme'; import { formatThousands } from '../../src/utils'; import type { JobDimensionScore } from '../../src/types'; /* ===================== 雷达图(6 维度,SVG 绘制) ===================== */ /** 六边形雷达图:同心网格 + 轴线 + 数据多边形 + 维度标签 */ function RadarChart({ dimensions, size = 160, }: { dimensions: JobDimensionScore[]; size?: number; }) { const cx = size / 2; const cy = size / 2; const count = dimensions.length; // 数据/网格最大半径(预留标签空间) const R = size / 2 - 36; const labelR = R + 16; const levels = [0.25, 0.5, 0.75, 1]; const angleFor = (i: number) => (-90 + i * (360 / count)) * (Math.PI / 180); const pointFor = (i: number, ratio: number) => ({ x: cx + R * ratio * Math.cos(angleFor(i)), y: cy + R * ratio * Math.sin(angleFor(i)), }); const polygonPoints = (ratio: number) => dimensions .map((_, i) => { const p = pointFor(i, ratio); return `${p.x},${p.y}`; }) .join(' '); const dataPoints = dimensions.map((d, i) => pointFor(i, d.value / 100)); return ( {/* 同心网格 */} {levels.map((ratio) => ( ))} {/* 轴线 */} {dimensions.map((_, i) => { const p = pointFor(i, 1); return ( ); })} {/* 数据多边形 */} `${p.x},${p.y}`).join(' ')} fill={colors.primary} fillOpacity={0.15} stroke={colors.primary} strokeWidth={2} /> {/* 数据点 */} {dataPoints.map((p, i) => ( ))} {/* 维度标签 */} {dimensions.map((d, i) => { const lx = cx + labelR * Math.cos(angleFor(i)); const ly = cy + labelR * Math.sin(angleFor(i)); return ( {d.name} ); })} ); } /* ===================== 子组件 ===================== */ /** 顶部导航:返回 + 标题 + 分享 */ function Header({ onBack }: { onBack: () => void }) { return ( 岗位详情 Alert.alert('分享', '分享功能开发中,敬请期待')} hitSlop={8}> ); } /** 分区标题:竖条装饰 + 标题 + 可选右侧链接 */ function SectionTitle({ title, link, onLink, }: { title: string; link?: string; onLink?: () => void; }) { return ( {title} {link ? ( {link} > ) : null} ); } /* ===================== 页面 ===================== */ export default function JobDetailScreen() { const { id } = useLocalSearchParams<{ id: string }>(); const router = useRouter(); const { detail, overallScore, salaryData, reviews, loading, error, refresh } = useJobDetail(id); const [comment, setComment] = useState(''); // 错误态 if (error) { return (
router.back()} /> 加载失败,请重试 重试 ); } // 加载态 if (loading || !detail || !overallScore || !salaryData) { return (
router.back()} /> ); } return (
router.back()} /> {/* 1. 岗位基本信息 */} {detail.positionName} {detail.status} 浏览{formatThousands(detail.views)} {detail.salaryMin}-{detail.salaryMax}K · {detail.salaryMonths}薪 {detail.companyName} · {detail.industry} · {detail.companySize} {detail.companyLocation} · {detail.experience} · {detail.education} {detail.benefits.map((item) => ( {item} ))} {/* 2. 岗位综合评分 */} Alert.alert('评分说明', '评分规则说明开发中,敬请期待')} /> {overallScore.totalScore} 分 {overallScore.level} {overallScore.rank} router.push('/jobs/compare')} hitSlop={6}> 加入对比 {/* 3. 真实薪资区间 */} Alert.alert('薪资详情', '完整薪资数据开发中,敬请期待')} /> {salaryData.ranges.map((range, index) => ( {range.experience} {range.min}-{range.max}K ))} {salaryData.dataSource} {/* 4. 员工评价 */} {reviews.map((review) => ( {review.userName} {review.userRole} {review.time} {review.content} {review.tags.map((tag) => ( {tag} ))} ))} {/* 5. 评论输入框(固定底部) */} Alert.alert('发布评论', '评论发布功能开发中,敬请期待')}> 发布 ); } // 跨平台阴影 const shadow = { shadowColor: colors.textPrimary, shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.06, shadowRadius: 8, elevation: 3, }; const styles = StyleSheet.create({ safeArea: { flex: 1, backgroundColor: colors.pageBg, }, stateWrap: { flex: 1, alignItems: 'center', justifyContent: 'center', }, stateText: { marginTop: 12, fontSize: 14, color: colors.textSecondary, }, retryBtn: { marginTop: 16, paddingHorizontal: 24, paddingVertical: 8, borderRadius: 18, backgroundColor: colors.primaryBtn, }, retryText: { fontSize: 13, fontWeight: '600', color: colors.white, }, /* ---- 顶部导航 ---- */ header: { height: 48, flexDirection: 'row', alignItems: 'center', justifyContent: 'center', }, headerTitle: { fontSize: 17, fontWeight: '700', color: colors.textPrimary, }, headerBtn: { position: 'absolute', top: 0, width: 48, height: 48, alignItems: 'center', justifyContent: 'center', }, scroll: { flex: 1, }, content: { paddingHorizontal: 20, paddingBottom: 24, }, card: { backgroundColor: colors.white, borderRadius: 16, padding: 16, marginBottom: 16, ...shadow, }, /* ---- 基本信息 ---- */ basicTop: { flexDirection: 'row', alignItems: 'flex-start', justifyContent: 'space-between', }, positionName: { flex: 1, fontSize: 22, fontWeight: '700', color: colors.textPrimary, }, statusCol: { alignItems: 'flex-end', marginLeft: 12, }, statusTag: { backgroundColor: colors.successLight, paddingHorizontal: 8, paddingVertical: 3, borderRadius: 10, }, statusText: { fontSize: 12, fontWeight: '600', color: colors.successText, }, viewsText: { marginTop: 6, fontSize: 12, color: colors.textHint, }, salary: { marginTop: 14, fontSize: 20, fontWeight: '700', color: colors.primary, }, metaText: { marginTop: 8, fontSize: 13, color: colors.textSecondary, }, benefits: { flexDirection: 'row', flexWrap: 'wrap', marginTop: 12, }, benefitTag: { backgroundColor: colors.primaryLight, borderWidth: 1, borderColor: colors.primaryBorder, borderRadius: 12, paddingHorizontal: 10, paddingVertical: 4, marginRight: 8, marginBottom: 8, }, benefitText: { fontSize: 12, color: colors.primary, }, /* ---- 分区标题 ---- */ sectionHeader: { flexDirection: 'row', alignItems: 'center', marginBottom: 12, }, sectionBar: { width: 4, height: 16, borderRadius: 2, backgroundColor: colors.primary, marginRight: 8, }, sectionTitle: { flex: 1, fontSize: 17, fontWeight: '700', color: colors.textPrimary, }, sectionLinkBtn: { paddingLeft: 8, }, sectionLink: { fontSize: 13, color: colors.primary, }, /* ---- 综合评分 ---- */ scoreRow: { flexDirection: 'row', alignItems: 'center', }, scoreInfo: { flex: 1, alignItems: 'flex-start', marginLeft: 8, }, scoreNumberRow: { flexDirection: 'row', alignItems: 'baseline', }, scoreNumber: { fontSize: 40, fontWeight: '700', color: colors.primary, }, scoreUnit: { fontSize: 14, fontWeight: '600', color: colors.primary, }, levelTag: { marginTop: 4, backgroundColor: colors.successLight, paddingHorizontal: 10, paddingVertical: 4, borderRadius: 12, }, levelTagText: { fontSize: 13, fontWeight: '600', color: colors.successText, }, scoreRank: { marginTop: 8, fontSize: 12, color: colors.textSecondary, }, compareBtn: { flexDirection: 'row', alignItems: 'center', alignSelf: 'flex-start', marginTop: 16, }, compareText: { marginLeft: 4, fontSize: 14, fontWeight: '600', color: colors.primary, }, /* ---- 薪资区间 ---- */ salaryRow: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', paddingVertical: 12, borderBottomWidth: 1, borderBottomColor: colors.borderLight, }, salaryRowLast: { borderBottomWidth: 0, }, salaryExp: { fontSize: 14, color: colors.textSecondary, }, salaryRange: { fontSize: 15, fontWeight: '700', color: colors.textPrimary, }, salarySource: { marginTop: 12, fontSize: 12, color: colors.textHint, }, /* ---- 员工评价 ---- */ reviewHeader: { flexDirection: 'row', alignItems: 'center', }, reviewName: { fontSize: 14, fontWeight: '700', color: colors.textPrimary, }, reviewRole: { marginLeft: 8, fontSize: 12, color: colors.textSecondary, }, reviewTime: { flex: 1, textAlign: 'right', fontSize: 12, color: colors.textHint, }, reviewContent: { marginTop: 10, fontSize: 13, lineHeight: 20, color: colors.textPrimary, }, reviewTags: { flexDirection: 'row', flexWrap: 'wrap', marginTop: 10, }, reviewTag: { backgroundColor: colors.pageBg, borderWidth: 1, borderColor: colors.border, borderRadius: 12, paddingHorizontal: 10, paddingVertical: 4, marginRight: 8, marginBottom: 8, }, reviewTagText: { fontSize: 12, color: colors.textSecondary, }, /* ---- 评论输入框 ---- */ commentBar: { flexDirection: 'row', alignItems: 'center', paddingHorizontal: 16, paddingVertical: 10, backgroundColor: colors.white, borderTopWidth: 1, borderTopColor: colors.borderLight, }, commentInput: { flex: 1, height: 40, borderRadius: 20, backgroundColor: colors.pageBg, paddingHorizontal: 16, fontSize: 14, color: colors.textPrimary, }, publishBtn: { marginLeft: 12, paddingHorizontal: 18, height: 40, borderRadius: 20, alignItems: 'center', justifyContent: 'center', backgroundColor: colors.primaryBtn, }, publishText: { fontSize: 14, fontWeight: '600', color: colors.white, }, });